/*
*  gconvo.c - Conversation window
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <string.h>
#include <gdk/gdkkeysyms.h>

#include "gconvo.h"
#include "eventproc.h"
#include "msn/utility.h"

void gconvo_draw_window (GConvo *convo);
void gconvo_create_text_tags (GConvo *convo);
static gboolean on_entry_keypress (GtkWidget *widget, GdkEventKey *event, GConvo *convo);
static void on_send_clicked (GtkWidget *widget, GConvo *convo);
static gboolean on_window_closed (GtkWidget *widget, GdkEvent *event, GConvo *convo);

int request_new_switchboard (GConvo *convo);
int gconvo_send_message (GConvo *convo, gchar *msg);
int gconvo_set_primary_contact (GConvo *convo, MsnContact *contact);
void gconvo_disconnect (GConvo *convo);
void gconvo_remove_participant (GConvo *convo, MsnContact *contact);

int setup_callbacks (GConvo *convo);

#define MSG_ERROR "Message could not be sent.\n"
#define MSG_LENGTH_ERROR "Message could not be sent because it is too long.\n"
#define MSG_HIDDEN_WARN "Warning: You have set your status to Hidden, but this \
conversation is still active. You will be able to send and receive messages. \
while it remains active\n"
#define MSG_PRIMARY_OFFLINE_SEND_ERROR "Message could not be sent because the \
contact is offline.\n"
#define MSG_HIDDEN_SEND_ERROR "Message cannot be sent while you status is set \
to Hidden.\n"
/* ---------- Initialization ---------- */

GConvo *
gconvo_new (MsnSession *session, MsnSwitchboardType type)
{
	GConvo *convo = NULL;

	g_return_val_if_fail (session != NULL, NULL);

	convo = g_new (GConvo, 1);
	convo->sboard = NULL;
	convo->session = session;
	convo->primary_contact = NULL;
	convo->participants = NULL;
	convo->msg_queue = g_queue_new ();
	convo->connected = FALSE;
	convo->connecting = FALSE;
	convo->cb_data = NULL;
	convo->closed_cb = NULL;
	convo->num_participants = 0;
	convo->status_label = NULL;
	convo->contact_label = NULL;
	convo->typing_timeout_id = 0;

	if (type == SWITCHBOARD_CHAT_NEW) {
		if (session->acct->status != HIDDEN)
			request_new_switchboard (convo);
	}

	gconvo_draw_window (convo);
	gconvo_create_text_tags (convo);

	return convo;
}

void
gconvo_close (GConvo *convo)
{
	g_return_if_fail (convo != NULL);

	if (convo->typing_timeout_id != 0) {
		g_source_remove (convo->typing_timeout_id);
		convo->typing_timeout_id = 0;
	}

	if (convo->connected)
		gconvo_disconnect (convo);

	if (convo->primary_contact != NULL) {
		msn_contact_free (convo->primary_contact);
		convo->primary_contact = NULL;
	}

	if (convo->msg_queue != NULL) {
		g_queue_free (convo->msg_queue);
		convo->msg_queue = NULL;
	}

	if (convo->participants != NULL) {
		g_list_free (convo->participants);
		convo->participants = NULL;
	}

	/* TODO: Free UI Stuff */
	gtk_widget_destroy ((GtkWidget *) convo->window);
	convo->window = NULL;
	convo->contact_label = NULL;
	convo->convo_view = NULL;
	convo->convo_buffer = NULL;
	convo->entry_view = NULL;
	convo->entry_buffer = NULL;
	convo->num_participants = 0;
	convo->status_label = NULL;

	/* Convo will be freed in event handler */
	if (convo->closed_cb != NULL)
		convo->closed_cb (convo, convo->cb_data);

	convo->closed_cb = NULL;
	convo->cb_data = NULL;

	g_free (convo);

	return;
}

void
gconvo_set_window_closed_cb (GConvo *convo, GConvoClosedCB func, gpointer data)
{
	g_return_if_fail (convo != NULL);
	g_return_if_fail (func != NULL);

	convo->closed_cb = func;
	convo->cb_data = data;
}

/* ---------- UI Draw ---------- */

void
gconvo_draw_window (GConvo *convo)
{  
	GtkWidget *window;
	GtkWidget *vbox;
	GtkWidget *align_contact_label;
	GtkWidget *contact_label;
	GtkWidget *hsep1;
	GtkWidget *vpaned;
	GtkWidget *vpaned_vbox;
	GtkWidget *hsep2;
	GtkWidget *frame;
	GtkWidget *hpaned;

	GtkWidget *convo_scrolledwindow;
	GtkWidget *convo_textview;

	GtkWidget *entry_scrolledwindow;
	GtkWidget *entry_textview;
	GtkWidget *send_button;
	GtkWidget *align_status_label;
	GtkWidget *status_label;

	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title (GTK_WINDOW (window), "Conversation");

	gtk_window_set_default_size ((GtkWindow *)window, 500, 450);

	/* Vbox that holds everything */
	vbox = gtk_vbox_new (FALSE, 0);

	/* Top portion */
	/* Label and alignment */
	contact_label = gtk_label_new (NULL);
	gtk_label_set_use_markup (GTK_LABEL (contact_label), TRUE);
	gtk_label_set_line_wrap (GTK_LABEL (contact_label), TRUE);
	align_contact_label = gtk_alignment_new (0, 0, 0, 0);
	gtk_alignment_set_padding (GTK_ALIGNMENT (align_contact_label), 5, 5, 5, 10);

	/* Separators */
	hsep1 = gtk_hseparator_new ();
	hsep2 = gtk_hseparator_new ();

	gtk_container_add (GTK_CONTAINER (align_contact_label), contact_label);
	gtk_box_pack_start (GTK_BOX (vbox), align_contact_label, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (vbox), hsep1, FALSE, FALSE, 0);

	/* Convo view */
	/* Vbox that holds scrolledwindow and hsep2 */
	vpaned_vbox = gtk_vbox_new (FALSE, 0);

	convo_scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (convo_scrolledwindow),
		GTK_POLICY_AUTOMATIC,
		GTK_POLICY_AUTOMATIC);

	convo_textview = gtk_text_view_new ();
	gtk_text_view_set_editable (GTK_TEXT_VIEW (convo_textview), FALSE);
	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (convo_textview), FALSE);
	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (convo_textview), GTK_WRAP_WORD);
	gtk_text_view_set_left_margin (GTK_TEXT_VIEW (convo_textview), 4);
	gtk_text_view_set_pixels_below_lines (GTK_TEXT_VIEW (convo_textview), 2);

	gtk_container_add (GTK_CONTAINER (convo_scrolledwindow),
		convo_textview);

	gtk_box_pack_start (GTK_BOX (vpaned_vbox), convo_scrolledwindow, TRUE, TRUE, 0);
	gtk_box_pack_start (GTK_BOX (vpaned_vbox), hsep2, FALSE, FALSE, 0);

	/* Bottom portion */
	hpaned = gtk_hpaned_new ();
	frame = gtk_frame_new (NULL);
	gtk_paned_set_position ((GtkPaned *)hpaned, 400);

	entry_scrolledwindow = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (entry_scrolledwindow),
		GTK_POLICY_NEVER,
		GTK_POLICY_AUTOMATIC);

	entry_textview = gtk_text_view_new ();
	gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (entry_textview), GTK_WRAP_WORD);
	gtk_text_view_set_left_margin (GTK_TEXT_VIEW (entry_textview), 2);
	
	gtk_container_add (GTK_CONTAINER (entry_scrolledwindow), entry_textview);

	gtk_container_add (GTK_CONTAINER (frame), entry_scrolledwindow);

	send_button = gtk_button_new_with_label ("Send");

	gtk_paned_pack1 (GTK_PANED (hpaned), frame, FALSE, TRUE);
	gtk_paned_pack2 (GTK_PANED (hpaned), send_button, FALSE, TRUE);

	/* Status Label */
	status_label = gtk_label_new (NULL);
	gtk_label_set_single_line_mode (GTK_LABEL (status_label), TRUE);
	align_status_label = gtk_alignment_new (0, 0, 0, 0);
	gtk_alignment_set_padding (GTK_ALIGNMENT (align_status_label), 5, 5, 5, 10);

	gtk_container_add (GTK_CONTAINER (align_status_label), status_label);

	/* Pack everything */
	vpaned = gtk_vpaned_new ();
	/* Set position to 70% of height */
	/* 70% of 450 = 315 */
	gtk_paned_set_position (GTK_PANED (vpaned), 315);
	gtk_paned_pack1 (GTK_PANED (vpaned), vpaned_vbox, FALSE, TRUE);
	gtk_paned_pack2 (GTK_PANED (vpaned), hpaned, FALSE, TRUE);

	gtk_container_add (GTK_CONTAINER (vbox), vpaned);
	gtk_box_pack_start(GTK_BOX (vbox), align_status_label, FALSE, FALSE, 0);
	gtk_container_add (GTK_CONTAINER (window), vbox);

	/* Focus */
	gtk_widget_grab_focus (entry_textview);

	/* Signals */
	g_signal_connect (G_OBJECT (entry_textview),
		"key-press-event",
		G_CALLBACK (on_entry_keypress),
		convo);
	g_signal_connect (G_OBJECT (send_button),
		"clicked",
		G_CALLBACK (on_send_clicked),
		convo);
	gtk_signal_connect (GTK_OBJECT (window),
		"delete_event",
		G_CALLBACK (on_window_closed),
		convo);

	convo->window = GTK_WINDOW (window);
	convo->contact_label = GTK_LABEL (contact_label);
	convo->entry_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry_textview));
	convo->entry_view = GTK_TEXT_VIEW (entry_textview);
	convo->convo_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (convo_textview));
	convo->convo_view = GTK_TEXT_VIEW (convo_textview);
	convo->status_label = GTK_LABEL (status_label);

	/* Text Tags */
	/*gdk_color_parse ("#1C86EE", &color);
	tag = gtk_text_buffer_create_tag (convo->convo_buffer,
	"local_user_tag",
	"weight-set", TRUE,
	"weight", PANGO_WEIGHT_BOLD,
	"foreground-set", TRUE,
	"foreground-gdk", &color,
	NULL);*/
}

void
gconvo_create_text_tags (GConvo *convo)
{
	GtkTextBuffer *buffer = NULL;
	GtkTextTag *tag;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (convo->convo_view != NULL);

	buffer = gtk_text_view_get_buffer (convo->convo_view);

	g_return_if_fail (buffer != NULL);

	tag = gtk_text_buffer_create_tag (buffer,
		"send_message",
		"foreground", "blue",
		"foreground-set", TRUE,
		"weight", PANGO_WEIGHT_BOLD,
		"weight-set", TRUE,
		NULL);

	tag = gtk_text_buffer_create_tag (buffer,
		"receive_message",
		"foreground", "red",
		"foreground-set", TRUE,
		"weight", PANGO_WEIGHT_BOLD,
		"weight-set", TRUE,
		NULL);

	tag = gtk_text_buffer_create_tag (buffer,
		"information",
		"weight", PANGO_WEIGHT_BOLD,
		"weight-set", TRUE,
		"scale", PANGO_SCALE_LARGE,
		"scale-set", TRUE,
		NULL);

	tag = gtk_text_buffer_create_tag (buffer,
		"error",
		"foreground", "red",
		"foreground-set", TRUE,
		"weight", PANGO_WEIGHT_BOLD,
		"weight-set", TRUE,
		"scale", PANGO_SCALE_LARGE,
		"scale-set", TRUE,
		NULL);

	return;
}

void
gconvo_show (GConvo *convo)
{
	gtk_widget_show_all ((GtkWidget *) convo->window);
}

/* ---------- UI Interaction ---------- */

int
entry_text_get_contents (GConvo *convo, gchar **buffer)
{
	GtkTextIter start;
	GtkTextIter end;

	g_return_val_if_fail (convo != NULL, -1);
	g_return_val_if_fail (buffer != NULL, -1);

	gtk_text_buffer_get_start_iter (convo->entry_buffer, &start);
	gtk_text_buffer_get_end_iter (convo->entry_buffer, &end);

	*buffer = gtk_text_buffer_get_text (convo->entry_buffer,
		&start,
		&end,
		FALSE);

	/* Clear the entry buffer */
	gtk_text_buffer_delete (convo->entry_buffer, &start, &end);

	return 0;
}

void
gconvo_text_append (GtkTextView *view, const gchar *text, const gchar *tag_name)
{
	GtkTextBuffer *buffer = NULL;
	GtkTextIter iter;

	g_return_if_fail (view != NULL);
	g_return_if_fail (text != NULL);

	buffer = gtk_text_view_get_buffer (view);
	if (buffer == NULL)
		return;

	/* Write to buffer */
	gtk_text_buffer_get_end_iter (buffer, &iter);
	if (tag_name != NULL)
		gtk_text_buffer_insert_with_tags_by_name (buffer, 
		&iter,
		text,
		strlen (text),
		tag_name,
		NULL);
	else
		gtk_text_buffer_insert (buffer, &iter, text, strlen (text));

	/* Scroll to end of buffer */
	gtk_text_buffer_get_end_iter (buffer, &iter);
	gtk_text_view_scroll_to_iter (view, &iter, 0, TRUE, 0.0, 1.0);
}

void
gconvo_refresh_info (GConvo *convo)
{
	MsnContact *primary = NULL;
	g_return_if_fail (convo != NULL);

	/* Update contact label */
	primary = convo->primary_contact;
	if (primary != NULL) {
		char *status_str = NULL;
		char *nickname = NULL;
		char *disp_str = NULL;
		char *final = NULL;

		status_str = get_string_from_status (primary->status);
		g_assert (status_str != NULL);

		nickname = g_markup_escape_text (primary->nickname,
			strlen (primary->nickname));

		disp_str = g_strdup_printf ("<b>%s</b> (%s) - <i>%s</i>",
			nickname,
			primary->passport,
			status_str);

		gtk_label_set_markup (convo->contact_label, disp_str);


		g_free (status_str);
		g_free (nickname);
		g_free (disp_str);
	}

}

void
gconvo_warn_status_hidden (GConvo *convo)
{
	g_return_if_fail (convo != NULL);
	gconvo_text_append (convo->convo_view, MSG_HIDDEN_WARN, "information");
}

/* ---------- UI Callbacks ---------- */

static gboolean
on_entry_keypress (GtkWidget *widget, GdkEventKey *event, GConvo *convo)
{
	g_return_val_if_fail (convo != NULL, FALSE);

	if (event->keyval == GDK_Return) {
		gchar *buffer = NULL;

		if (event->state & GDK_SHIFT_MASK) {
			/* 
			*	Shift-Enter should not send a message 
			Shift-Enter does not append a \r, so do it manually
			and do not process this event further
			*/
			gconvo_text_append (convo->entry_view, "\r\n", NULL);
			return TRUE;
		}

		entry_text_get_contents (convo, &buffer);
		gconvo_send_message (convo, buffer);
		g_free (buffer);


		return TRUE;
	}

	return FALSE;
}

void
on_send_clicked (GtkWidget *widget, GConvo *convo)
{
	gchar *buffer = NULL;

	g_return_if_fail (convo != NULL);

	entry_text_get_contents (convo, &buffer);
	gconvo_send_message (convo, buffer);

	g_free (buffer);
}

gboolean
on_window_closed (GtkWidget *widget, GdkEvent *event, GConvo *convo)
{
	g_return_val_if_fail (convo != NULL, FALSE);

	gtk_widget_destroy (GTK_WIDGET (convo->window));
	gconvo_close (convo);

	return FALSE;
}

/* ---------- Switchboard Management ---------- */

int
request_new_switchboard (GConvo *convo)
{
	MsnSwitchboard *sboard = NULL;
	int trid = 0;

	g_return_val_if_fail (convo != NULL, -1);

	msn_notification_request_switchboard (convo->session->notif);
	trid = convo->session->notif->conn->trid -1;
	sboard = msn_session_find_switchboard_from_id (convo->session, trid);

	if (sboard == NULL)
		return -1;

	convo->connecting = TRUE;
	gconvo_set_switchboard (convo, sboard);

	return 0;
}

int 
gconvo_set_primary_contact (GConvo *convo, MsnContact *contact)
{
	gchar *disp_str = NULL;
	gchar *status_str = NULL;

	g_return_val_if_fail (convo != NULL, -1);
	g_return_val_if_fail (contact != NULL, -1);
	g_return_val_if_fail (contact->passport != NULL, -1);
	g_return_val_if_fail (contact->nickname != NULL, -1);

	/* We have at least one contact */
	convo->primary_contact = contact;
	msn_contact_ref (contact);

	gconvo_refresh_info (convo);

	return 0;
}

void
gconvo_set_switchboard (GConvo *convo, MsnSwitchboard *sboard)
{
	g_return_if_fail (convo != NULL);
	g_return_if_fail (sboard != NULL);

	convo->sboard = sboard;
	setup_callbacks (convo);

	convo->connected = sboard->connected;
}

void
gconvo_disconnect (GConvo *convo)
{
	GList *list = NULL;
	g_return_if_fail (convo != NULL);

	if (convo->sboard != NULL) {
		msn_switchboard_free (convo->sboard);
		convo->sboard = NULL;
	}

	/* Free msg queue */
	if (g_queue_get_length (convo->msg_queue) > 0) {
		gchar *data = NULL;
		while (data = g_queue_pop_tail (convo->msg_queue))
			g_free (data);
	}

	/* Free participants list */
	list = convo->participants;
	while (list != NULL) {
		MsnContact *tmp = NULL;
		tmp = (MsnContact *) list->data;
		if (tmp == NULL)
			break;
		gconvo_remove_participant (convo, tmp);
		list = g_list_next (list);
	}

	convo->connected = FALSE;
	convo->connecting = FALSE;
}

int
gconvo_invite_participant (GConvo *convo, const char *passport)
{
	MsnContact *contact = NULL;
	int ret = 0;

	g_return_val_if_fail (convo != NULL, -1);

	g_assert (passport != NULL);

	contact = msn_clist_find_contact (convo->session->clist, passport);

	/* TODO: if passport is not in clist, contact will be null
	 * this prevents to chat with people not in the clist */
	if (convo->primary_contact == NULL) {
		gconvo_set_primary_contact (convo, contact);
	}

	ret = msn_switchboard_invite_user (convo->sboard, passport);

	return ret;
}

void
gconvo_add_participant (GConvo *convo, MsnContact *contact)
{
	g_return_if_fail (convo != NULL);
	g_return_if_fail (contact != NULL);

	convo->participants = g_list_append (convo->participants, contact);
	convo->num_participants = g_list_length (convo->participants);
	msn_contact_ref (contact);
}

void
gconvo_remove_participant (GConvo *convo, MsnContact *contact)
{
	g_return_if_fail (convo != NULL);
	g_return_if_fail (convo->participants != NULL);
	g_return_if_fail (contact != NULL);

	convo->participants = g_list_remove (convo->participants, contact);
	convo->num_participants = g_list_length (convo->participants);
	msn_contact_unref (contact);
}

void
msg_queue_add (GConvo *convo, gchar *msg)
{
	gchar *tmp = NULL;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (msg != NULL);

	tmp = g_strdup (msg);

	g_queue_push_head (convo->msg_queue, tmp);
}

void
process_msg_queue (GConvo *convo)
{
	gchar *buf = NULL;

	g_return_if_fail (convo != NULL);

	if (convo->msg_queue == NULL)
		return;

	buf = g_queue_pop_tail (convo->msg_queue);
	if (buf == NULL)
		return;

	gconvo_send_message (convo, buf);

	g_free (buf);
}

int
gconvo_send_message (GConvo *convo, gchar *msg)
{
	int ret = 0;
	g_return_val_if_fail (convo != NULL, -1);
	g_return_val_if_fail (msg != NULL, -1);

	/* Don't sent empty messages */
	if (strlen (msg) == 0)
		return 0;

	/* Make sure we have an active session before sending */
	if (!convo->connected) {

		if (convo->session->acct->status == HIDDEN) {
			/* We are not connected, and cannot connect 
			 * while hiding */
			gconvo_text_append (convo->convo_view,
					MSG_HIDDEN_SEND_ERROR,
					"error");
			return 0;
		}

		if (convo->primary_contact->status == OFFLINE) {
			/* TODO: this might be a prob with milti user chats */
			gconvo_text_append (convo->convo_view,
					MSG_PRIMARY_OFFLINE_SEND_ERROR,
					"error");
			return 0;
		}

		if (!convo->connecting) {
			/* We are not connecting to the switchboard */
			/* so start connecting */
			request_new_switchboard (convo);
			g_assert (convo->primary_contact != NULL);
			gconvo_invite_participant (convo, convo->primary_contact->passport);
		}

		/* Message will be sent once primary participant joins */
		msg_queue_add (convo, msg);
		return 0;
	} else if (convo->num_participants == 0) {
		/* We are connected, but no one has joined yet */
		/* So we wait */
		msg_queue_add (convo, msg);
		return 0;
	}

	ret = msn_switchboard_send_message (convo->sboard, msg);

	if (ret == 0) {		
		gconvo_text_append (convo->convo_view,
			convo->session->acct->nickname,
			"send_message");

		gconvo_text_append (convo->convo_view,
			":  ",
			"send_message");

		gconvo_text_append (convo->convo_view,
			msg,
			NULL);

		gconvo_text_append (convo->convo_view,
			"\r\n",
			NULL);
	}
	else if (ret == 1) {
		gconvo_text_append (convo->convo_view,
			MSG_LENGTH_ERROR,
			"error");
	}
	else {
		gconvo_text_append (convo->convo_view,
			MSG_ERROR,
			"error");
	}

	return ret;
}

/* ---------- Switchboard Callbacks ---------- */

void
on_connection (MsnSwitchboard *sboard, GConvo *convo)
{
	g_return_if_fail (sboard != NULL);
	g_return_if_fail (convo != NULL);

	convo->connected = TRUE;
	convo->connecting = FALSE;
}

void
on_disconnection (GConvo *convo, MsnEvent *event)
{
	g_return_if_fail (convo != NULL);

	g_print ("disconnected\n");

	gconvo_disconnect (convo);
}

void
on_contact_joined (GConvo *convo, MsnEvent *event)
{
	MsnContact *contact = NULL;
	gchar *text = NULL;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (event != NULL);

	contact = (MsnContact *) event->user_data;
	g_return_if_fail (contact != NULL);

	text = g_strdup_printf ("%s has joined the conversation.\r\n",
		contact->nickname);

	gconvo_text_append (convo->convo_view, text, "information");

	if (convo->primary_contact == NULL) {
		gconvo_set_primary_contact (convo, contact);
	}

	gconvo_add_participant (convo, contact);

	/* Send any messages in the queue */
	process_msg_queue (convo);

	g_free (text);
}

void
on_contact_left (GConvo *convo, MsnEvent *event)
{
	MsnContact *contact = NULL;
	gchar *text = NULL;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (event != NULL);

	contact = (MsnContact *) event->user_data;
	g_return_if_fail (contact != NULL);

	text = g_strdup_printf ("%s has left the conversation.\r\n",
		contact->nickname);

	gconvo_text_append (convo->convo_view, text, "information");

	g_free (text);

	gconvo_remove_participant (convo, contact);
}

void
on_message_received (GConvo *convo, MsnEvent *event)
{
	MsnCommand *line = NULL;
	MsnMessage *message = NULL;
	gchar *text = NULL;
	gchar *passport = NULL;
	gchar *nickname = NULL;
	MsnContact *contact = NULL;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (event != NULL);

	message = (MsnMessage *) event->user_data;
	g_return_if_fail (message != NULL);

	line = msn_command_from_string (message->msg_cmd);
	passport = url_decode (line->params [0]);
	contact = msn_clist_find_contact (convo->session->clist, passport);
	if (contact != NULL) {
		/* Use the most recent nickname for the contact */
		nickname = g_strdup (contact->nickname);
	}
	else
		nickname = url_decode (line->params [1]);

	/* Append the text that tag will affect */
	gconvo_text_append (convo->convo_view, nickname, "receive_message");
	gconvo_text_append (convo->convo_view, ":  ", "receive_message");
	gconvo_text_append (convo->convo_view, message->body, NULL);
	gconvo_text_append (convo->convo_view, "\r\n", NULL);


	/* Remove current typing notification */
	gtk_label_set_text (convo->status_label, NULL);

	g_free (passport);
	g_free (nickname);
	msn_command_free (line);
	msn_message_free (message);
}

gboolean
on_contact_typing_timeout (GConvo *convo)
{
	g_return_val_if_fail (convo != NULL, FALSE);

	gtk_label_set_text (convo->status_label, NULL);
	convo->typing_timeout_id = 0;

	return FALSE;
}

void
on_typing_notification (GConvo *convo, MsnEvent *event)
{
	MsnCommand *line = NULL;
	MsnContact *contact = NULL;
	MsnMessage *message = NULL;
	gchar *passport = NULL;
	gchar *nickname = NULL;
	gchar *text = NULL;
	guint timeout_id;

	g_return_if_fail (convo != NULL);
	g_return_if_fail (event != NULL);

	message = (MsnMessage *) event->user_data;
	g_return_if_fail (message != NULL);

	line = msn_command_from_string (message->msg_cmd);

	passport = url_decode (line->params [0]);
	contact = msn_clist_find_contact (convo->session->clist, passport);
	if (contact != NULL) {
		/* Use the most recent nickname for the contact */
		nickname = g_strdup (contact->nickname);
	}
	else
		nickname = url_decode (line->params [1]);

	nickname = url_decode (line->params [1]);

	text = g_strdup_printf ("Typing User: %s\r\n", nickname);

	gtk_label_set_text (convo->status_label, text);

	if (convo->typing_timeout_id != 0)
		g_source_remove (convo->typing_timeout_id);

	/* Display typing notification for 6 seconds, or until a message is recieved */
	timeout_id = g_timeout_add (6000, on_contact_typing_timeout, convo);

	convo->typing_timeout_id = timeout_id;

	g_free (passport);
	g_free (nickname);
	g_free (text);
	msn_command_free (line);
	msn_message_free (message);
}

int
setup_callbacks (GConvo *convo)
{
	MsnSwitchboard *sboard = NULL;
	MsnCallbacks *cbs = NULL;

	g_return_val_if_fail (convo != NULL, -1);
	g_return_val_if_fail (convo->sboard != NULL, -1);

	sboard = convo->sboard;
	cbs = sboard->conn->callbacks;
	g_return_val_if_fail (cbs != NULL, -1);

	msn_callbacks_set_event_data (sboard->conn->callbacks, convo);

	msn_connection_set_connect_cb (sboard->conn, on_connection_established);
	msn_switchboard_set_connect_cb (sboard, on_connection);

	msn_callbacks_add_event (cbs, "contact_joined", on_contact_joined);
	msn_callbacks_add_event (cbs, "contact_left", on_contact_left);
	msn_callbacks_add_event (cbs, "message_received", on_message_received);
	msn_callbacks_add_event (cbs, "typing_notification", on_typing_notification);
	msn_callbacks_add_event (cbs, "disconnected", on_disconnection);

	return 0;
}
